#include <tcl.h>

#include "inspect.h"
#include "debug.h"

typedef struct Marker {
    Tcl_Obj *cmdName;
    int (*cmd)(ClientData data,Tcl_Interp *,int objc,Tcl_Obj *const objv[]);
} Marker;

static void marker_freeInt(Tcl_Obj *obj);
static void marker_updateString(Tcl_Obj *obj);

const Tcl_ObjType markerTypeD={
    "marker",
    marker_freeInt,
    NULL,//marker_dupInt,
    marker_updateString,
    NULL //marker_setFromAny
};
const Tcl_ObjType *markerType=&markerTypeD;
#define MARKER(obj) ((Marker *)&obj->internalRep.twoPtrValue)

static void
marker_freeInt(Tcl_Obj *obj)
{
    SHOULD(obj!=NULL,return);
    if (obj->typePtr==markerType) {
        if (MARKER(obj)->cmdName!=NULL) Tcl_DecrRefCount(MARKER(obj)->cmdName);
        obj->typePtr=NULL;
    }
}
static void
marker_updateString(Tcl_Obj *obj)
{
    SHOULD(obj!=NULL,return);
    obj->bytes=Tcl_Alloc(1);
    obj->bytes[0]=0;
    obj->length=0;
}


Tcl_Obj *
markerObjNew(Tcl_Obj *cmdName,int (*cmd)(ClientData data,Tcl_Interp *,int objc,Tcl_Obj *const objv[]))
{
    Tcl_Obj *markerObj;
    markerObj=Tcl_NewObj();
    Tcl_InvalidateStringRep(markerObj);
    markerObj->typePtr=markerType;
    MARKER(markerObj)->cmdName=cmdName;
    if (cmdName!=NULL) Tcl_IncrRefCount(cmdName);
    MARKER(markerObj)->cmd=cmd;
    return markerObj;
}
int
markerObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[])
{
    (void)data;
    if (objc==1) {
        /* вызов без аргументов - создаёт новый маркер */
        Tcl_Obj *result;
        int code;
        code=Tcl_Eval(interp,"info level 0");
        result=Tcl_GetObjResult(interp);
        if (code!=TCL_OK) {
            ERR("in info level 0");
            return TCL_ERROR;    
        }
        Tcl_Obj *cmdName;
        if (Tcl_ListObjIndex(interp,result,0,&cmdName)!=TCL_OK) {
            ERR("!!!");
            return TCL_ERROR;
        }
        Tcl_SetObjResult(interp,markerObjNew(cmdName,NULL));
        return TCL_OK;                         
    } else if (objc==2) {
        /* вызов с единственным аргументом - если передан маркер, возвращает его имя, иначе пустоту
         */
        if (objv[1]->typePtr==markerType) {
            Tcl_SetObjResult(interp,MARKER(objv[1])->cmdName);
        } else {
            Tcl_SetObjResult(interp,Tcl_NewObj());
        }
        return TCL_OK;
    }
    Tcl_WrongNumArgs(interp,objc,objv,"?obj?");
    return TCL_ERROR;
}
static void
cleanupMacroResult(ClientData data,Tcl_Interp *interp)
{
    (void)interp;
    if (data!=NULL)
        Tcl_DecrRefCount((Tcl_Obj *)data);
}
/** производит перестановки в строке, возвращает итоговую строку
*/
int
macroObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[])
{
    Tcl_Obj *result;
    Tcl_Obj *cmd;
    Tcl_Obj **cmd_objv;
    int cmd_objc;
    int t;
    int code;
    (void)data;
    if (objc==1) {
        result=Tcl_GetAssocData(interp,"macro",NULL);
        if (result!=NULL)
            Tcl_SetObjResult(interp,result);
        return TCL_OK;
    }
    cmd=Tcl_NewListObj(objc-1,objv+1);
    Tcl_IncrRefCount(cmd);
    do {
        Tcl_Obj *marker;
        Tcl_Obj *macro;
        Tcl_ListObjGetElements(interp,cmd,&cmd_objc,&cmd_objv);
        // аргументы есть - ищём позицию маркера
        // маркер на первой позиции имеет наивысший приоритет, прочие отрабатываются справа налево
        for(t=cmd_objc-1;t>=0;t--) {
            if (cmd_objv[t]->typePtr==markerType) break;
        }
        if (t<0) break;
        // TODO: вычислять длину аргументов
        marker=cmd_objv[t];
        //cmd_objv[t]=MARKER(marker)->cmdName;
        Tcl_IncrRefCount(cmd_objv[t]);
        macro=Tcl_NewListObj(cmd_objc-t,cmd_objv+t);
        Tcl_ListObjReplace(interp,macro,0,0,1,&MARKER(marker)->cmdName);
        Tcl_IncrRefCount(macro);
        //Tcl_IncrRefCount(macro);
        //code=Tcl_EvalObjv(interp,cmd_objc-t,cmd_objv+t,0);
        code=Tcl_EvalObjEx(interp,macro,TCL_EVAL_DIRECT);
        Tcl_DecrRefCount(macro);
        if (code!=TCL_OK) {
            Tcl_SetObjResult(interp,cmd);
            Tcl_DecrRefCount(marker);
            Tcl_DecrRefCount(cmd);
            return TCL_ERROR;
        }
        result=Tcl_GetObjResult(interp);
        // заменяем "хвост" на результат
        Tcl_ListObjReplace(interp,cmd,t,cmd_objc-t,0,NULL); // удаляем "хвост"
        Tcl_ListObjAppendList(interp,cmd,result);           // добавляем результат
        Tcl_DecrRefCount(marker);
    } while(1);
    Tcl_SetObjResult(interp,cmd);
    Tcl_DecrRefCount(cmd);
    return code;
}

/* поризводит макроподстановки, затем исполняет результат
*/
int
invokeObjCmd(ClientData data,Tcl_Interp *interp,int objc,Tcl_Obj *const objv[])
{
    Tcl_Obj *result;
    if (macroObjCmd(data,interp,objc,objv)!=TCL_OK)
        return TCL_ERROR;
    result=Tcl_GetObjResult(interp);
    Tcl_IncrRefCount(result);
    if (Tcl_EvalObjEx(interp,result,0)!=TCL_OK) return TCL_ERROR;
    return TCL_OK;
}
